iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 16
2
Modern Web

從0.5開始的JavaScript系列 第 16

Day16 舞DOM人生: 綁定方法、e 事件詳解

  • 分享至 

  • xImage
  •  

原本中午就要開工,但是到自己的主頁時,默默的就把個人背景打完,然後找了張還能看(?的照片放上去,不然都只有熊熊的圖案。
看能不能多幾個訂閱或追蹤/images/emoticon/emoticon25.gif


昨日講完了 JS 對於 DOM 常見的操作方法,那接下來就剩最後一個部分(遞mic~
好吧,台下沒有觀眾,只好我們自問自答 Yo

Day14 有提到,基本上在前端 JS 用於與使用者互動,像是使用者點了這個按鈕,接著 JS 進行後續動作。

也就是總共分為三個部分

  1. 按鈕
  2. 點選
  3. 後續動作

13 我們前兩天講完了,也就是能夠選取要操作的元素(那個按鈕)、以及對於該元素常用的操作方法(後續動作)。
剩下的就是今天要講的事件 Event (點選),有了按鈕還有準備要進行的動作,就只差了需要知道使用者何時點選的這個方法了。

Event(事件)

綁定方法

1. 寫在 HTML 標籤上

此種方法不推薦,雖然方便,但是程式碼一多時就會很髒,而且也難維護。

<h1 onclick="alert('clicked')">inline</h1>

2. 寫在 js 中

當事件觸發時,會自動把事件的詳細資訊傳入 callback function 中的第一個參數,通常會命名為 eevent

但是要非常注意,這種寫法同一個元素的同一個事件只能綁定一次
也就是下方範例中,點選 body 時,照理要依序跳出 Hi1 => Hi2,但是實際只會跳出 Hi2!

var myBody = document.body;

myBody.onclick = function(e){
  alert('Hi1');
};
myBody.onclick = function(e){
  alert('Hi2');
};

// Hi2

也可以將 callback function 設為具名函數

document.body.onclick = sayHi;
function sayHi(e){
  alert('Hi');
};

移除綁定

myBody.onclick = null;

3. addEventListener

addEventListener 不會有前一個綁定方法遇到的問題,也就是 body 綁定兩個 click 事件,也會依序跳出兩個 Hi。

var myBody = document.body;
myBody.addEventListener('click', function(e){
  alert('clicked');
}, false);

addEventListener 的第三個參數決定事件處理是 Event Bubbling(事件氣泡)還是 Event Capturing(事件捕捉)

  • false: 預設(可不寫),由內找到外。
  • true: 由外找到內。

這邊舉一個例子會比較清楚:

click me 時,預設(false)情況下,console 會先印出 li,再印出 ul
但是改成 true 時,則會先印出 ul,再印出 li

動手玩

<ul id="ul">
  <li id="li">click me</li>
</ul>
var ul = document.querySelector("#ul");
var li = document.querySelector("#li");

ul.addEventListener('click', function(e){
  console.log('ul clicked');
}, false);

li.addEventListener('click', function(e){
  console.log('li clecked');
}, false);

同樣也可以將 callback function 設為具名函數

document.body.addEventListener('click', sayHi);
function sayHi(e){
  alert('Hi');
};

移除綁定

document.body.removeEventListener('click', sayHi);

e 事件詳細資訊

上面介紹了 3 種綁定方法,當事件發生時,會有一個參數自動帶入 callback function,也就是我們常命名的 eevent

讓我們接著看看這個事件詳細資訊有什麼用法吧。

stopPropagation 中止冒泡事件

上一個例子,點 click me 時,會先顯示 li,再往外顯示 ul
很多情況會遇到這種元素重疊的問題,當我們只想顯示點擊的 li 時,可以把冒泡中止,不讓它再往外找。

ul.addEventListener('click', function(e){
  console.log('ul clicked');
}, false);

li.addEventListener('click', function(e){
  e.stopPropagation(); // 此處終止了冒泡事件
  console.log('li clecked');
}, false);

preventDefault 取消預設行為

有些元素會有默認行為,像是 a 連結 點它會自動跳轉、submit 點擊會自動把表單內容送出。
當我們不想讓它的預設動作執行時,可以取消它,像是前端想先檢查表單內容是否正確再傳到後端,所以要阻止 submit 點擊後就把內容送出。

<a href="http://www.google.com" id="link">myLink</a>
var link = document.querySelector("#link");
link.addEventListener('click', function(e){
  e.preventDefault(); // 此處阻止跳轉到 google.com
  console.log('link no jump');
}, false);

target 取得點擊的元素

當然 e 裡面也可以取得觸發該事件的元素資訊。

EX:
我們將 click 事件綁定在最外面的 div,接著點選 div 中的元素,範例中點選 li 的連結,
可以看到 callback function 中分別印出該元素的結構、元素名、ID...。

動手玩

<div class="header">
  <ul style="border: 1px solid black; padding: 10px">
    <li><a href="#" id="link">123</a></li>
  </ul>
</div>
var header = document.querySelector(".header");
header.addEventListener('click', function(e){
  // console.log(e.target);
  
  // EX: LI
  console.log(e.target.nodeName);
  
  // EX: link
  console.log(e.target.id);
});

優化綁定,由父元素監聽子元素

<div class="header">
  <ul style="border: 1px solid black; padding: 10px; list-style: none">
    <li>1</li>
    <li>2</li>
    <li>3</li>
  </ul>
</div>

Q: 請問若是想顯示點擊的 li 內容該怎麼做?
A: 綁定每個 li 再顯示。

動手玩

var li = document.querySelectorAll('.header ul li');
for(var i = 0; i < li.length; i++){
  li[i].addEventListener('click', showText);
}
function showText(e){
  console.log(e.target.textContent);
}

But

但是這樣需要個別綁定,效率較低,而且動態新增的元素不會被綁定

較好的做法是綁定 li 的父元素來監聽,也就是結合上方提到的 e.target

動手玩

var header = document.querySelector(".header");

header.addEventListener('click', function(e){
  if(e.target.nodeName == "LI"){
    console.log(e.target.textContent);
  }
});

取得滑鼠座標位置

  • screen 計算滑鼠在整個螢幕之位置(算入解析度)
  • page 計算在瀏覽器中的網頁頁面之位置
  • client 計算在瀏覽器中的位置

知道滑鼠座標也可以把滑鼠圖示改變(覆蓋)成自訂圖案XD

動手玩

<body style="min-height: 100vh; cursor: none">
  <div>
    <p>
      screenX: <span class="screenX"></span>
      screenY: <span class="screenY"></span> 
    </p>
    <p>
      pageX: <span class="pageX"></span>
      pageY: <span class="pageY"></span> 
    </p>
    <p>
      clientX: <span class="clientX"></span>
      clientY: <span class="clientY"></span>
    </p>
  </div>

  <div class="mouseImg" style="position: absolute">
    <img src="https://upload.wikimedia.org/wikipedia/commons/1/1b/Creative-Tail-Animal-dog.svg" width="50px">
  </div>
</body>
var screenX = document.querySelector('.screenX');
var screenY = document.querySelector('.screenY');
var pageX = document.querySelector('.pageX');
var pageY = document.querySelector('.pageY');
var clientX = document.querySelector('.clientX');
var clientY = document.querySelector('.clientY');
var mouseImg = document.querySelector('.mouseImg');

var body = document.body;
body.addEventListener('mousemove', getPosition, false);

function getPosition(e){
  screenX.textContent = e.screenX;
  screenY.textContent = e.screenY;
  pageX.textContent = e.pageX;
  pageY.textContent = e.pageY;
  clientX.textContent = e.clientX;
  clientY.textContent = e.clientY;

  mouseImg.style.left = e.clientX + 'px';
  mouseImg.style.top = e.clientY + 'px';
}

原本想要一次打完,但發現篇幅有點多,決定把常用的事件放到明天來介紹。

今日的分享就到這,我們明天見/images/emoticon/emoticon51.gif


上一篇
Day15 舞DOM人生: JS與HTML/CSS/Attr & What's XSS?
下一篇
Day17 舞DOM人生: 常用事件大全
系列文
從0.5開始的JavaScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言